﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace XmlVisualizer.DiagramDesigner.Controls {
    /// <summary>
    /// Represents the panel with objects.
    /// </summary>
    public class RelativePositionPanel : Panel {
		#region Dependency properties
		/// <summary>
		/// Identifies the XmlVisualizer.DiagramDesigner.Controls.RelativePositionPanel.RelativePositionProperty.
		/// </summary>
		public static readonly DependencyProperty RelativePositionProperty =
			DependencyProperty.RegisterAttached("RelativePosition", typeof(Point), typeof(RelativePositionPanel),
			new FrameworkPropertyMetadata(new Point(0, 0),
										  new PropertyChangedCallback(RelativePositionPanel.OnRelativePositionChanged)));

		/// <summary>
		/// Gets the relative position of the element.
		/// </summary>
		/// <param name="element">UIElement which position to get.</param>
		/// <returns>Point with UIElement's position.</returns>
		/// <exception cref="System.ArgumentNullException">element is null.</exception>
		public static Point GetRelativePosition(UIElement element) {
			if (element == null) {
				throw new ArgumentNullException("element");
			}
			return (Point)element.GetValue(RelativePositionProperty);
		}

		/// <summary>
		/// Sets the relative position of the element to the position value.
		/// </summary>
		/// <param name="element">UIElement which position to set.</param>
		/// <param name="value">Position to set.</param>
		/// <exception cref="System.ArgumentNullException">element is null.</exception>
		public static void SetRelativePosition(UIElement element, Point value) {
			if (element == null) {
				throw new ArgumentNullException("element");
			}
			element.SetValue(RelativePositionProperty, value);
		}
		#endregion		

		#region Event handlers
		/// <summary>
		/// RelativePositionChanged event handler.
		/// </summary>
		/// <param name="d">The DependencyObject on which the property has changed value.</param>
		/// <param name="e">Event data that is issued by any event that tracks changes to the effective value of this property.</param>
		private static void OnRelativePositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
			UIElement reference = d as UIElement;
			if (reference != null) {
				RelativePositionPanel parent = VisualTreeHelper.GetParent(reference) as RelativePositionPanel;
				if (parent != null) {
					parent.InvalidateArrange();
				}
			}
		}

		/// <summary>
		/// Overrides default arrange.
		/// </summary>
		/// <param name="arrangeSize">The computed size that is used to arrange the content.</param>
		/// <returns>The size of the control.</returns>
		protected override Size ArrangeOverride(Size arrangeSize) {
			foreach (UIElement element in base.InternalChildren) {
				if (element != null) {
					Point relPosition = GetRelativePosition(element);
					double x = (arrangeSize.Width - element.DesiredSize.Width) * relPosition.X;
					double y = (arrangeSize.Height - element.DesiredSize.Height) * relPosition.Y;

					if (double.IsNaN(x))
						x = 0;
					if (double.IsNaN(y))
						y = 0;

					element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
				}
			}
			return arrangeSize;
		}

		/// <summary>
		/// Determines the required size of the RelativePositionPanel within the visual tree of the logical parent.
		/// </summary>
		/// <param name="availableSize">The available size that this element can give to the child. You can use infinity as a value to indicate that the element can size to whatever content is available.</param>
		/// <returns>A Size structure that has the Height and Width properties both equal to zero (0).</returns>
		protected override Size MeasureOverride(Size availableSize) {
			Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);

			// SDK docu says about InternalChildren Property: 'Classes that are derived from Panel 
			// should use this property, instead of the Children property, for internal overrides 
			// such as MeasureCore and ArrangeCore.

			foreach (UIElement element in this.InternalChildren) {
				if (element != null)
					element.Measure(size);
			}

			return base.MeasureOverride(availableSize);
		}
		#endregion
    }
}
